/* 
 * Licensed to the Zilbo.com (zilbo) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Zilbo licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.

   jsmin.c
   2007-12-04

Originally Copyright (c) 2002 Douglas Crockford  (www.crockford.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

This was converted to an apache module by Ian Holsman (c) 2008

*/
#ident "$HeadURL: http://modjsmin.googlecode.com/svn/trunk/mod_jsmin.c $"
#ident "$LastChangedBy: kryton $"
#ident "$LastChangedDate: 2008-08-27 23:41:44 +0000 (Wed, 27 Aug 2008) $"
#ident "$LastChangedRevision: 10 $"
#ident "$Id: mod_jsmin.c 10 2008-08-27 23:41:44Z kryton $"

#include "apr_strings.h"
#include "apr_fnmatch.h"
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_file_io.h"
#include "apr_pools.h"
#include "apr_general.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"

#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_request.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"

#include "mod_core.h"

typedef signed char UTF8;

module AP_MODULE_DECLARE_DATA jsmin_module;
/* This can be converted to a directive later if required, 5242880 = 5MB*/
#define JSMIN_MAX_BUFFER_SIZE 5242880 
#define JSMIN_BUFFER_SIZE 20000

typedef struct {
//    apr_file_t *infile;
    apr_pool_t *p;
    int lookahead;
    int A;
    int B;
    apr_size_t inlength;
    int inbufpos;
    apr_off_t outlength;
    int outbufpos;
    UTF8 *inbuf;
    UTF8 *outbuf;

} inbuffer;

 

static apr_status_t jsmin_int( inbuffer *inbuf, apr_bucket_brigade *bb);

/****************************************************************
 *
 * Handling configuration directives...
 */

typedef struct jsmin_config_struct {
	int enabled;
} jsmin_config_rec;

typedef struct jsmin_ctx_t
{
    apr_bucket_brigade *bb;
    apr_pool_t *p;
} jsmin_ctx;

static int bb_rputc( inbuffer *buf, UTF8 c, apr_bucket_brigade *bb)
{
//    char *c2 = (char)c;
    buf->outbuf[buf->outbufpos++] = c;
//    ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OUT - %c %xc", c,c );
    buf->outlength++;
    if (buf->outbufpos >= (JSMIN_BUFFER_SIZE)) {
        apr_bucket *b = apr_bucket_transient_create( (char*)buf->outbuf, 
                buf->outbufpos*sizeof(UTF8), bb->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, b);    
        buf->outbuf = apr_palloc( buf->p, JSMIN_BUFFER_SIZE );
        buf->outbufpos=0;
    }
}


static const command_rec jsmin_cmds[] =
{
   
    AP_INIT_FLAG("jsmin_enable", ap_set_flag_slot,
                  (void *)APR_OFFSETOF(jsmin_config_rec, enabled),
                  OR_INDEXES, "enable jsmin in this location"),
    {NULL}
};

static void *create_jsmin_config(apr_pool_t *p, char *dummy)
{
    jsmin_config_rec *new =
    (jsmin_config_rec *) apr_pcalloc(p, sizeof(jsmin_config_rec));

    new->enabled = 2; 

    return (void *) new;
}

static void *merge_jsmin_configs(apr_pool_t *p, void *basev, void *addv)
{
    jsmin_config_rec *new;
    jsmin_config_rec *base = (jsmin_config_rec *) basev;
    jsmin_config_rec *add = (jsmin_config_rec *) addv;

    new = (jsmin_config_rec *) apr_pcalloc(p, sizeof(jsmin_config_rec));
	if (add->enabled == 2) {
		new->enabled = base->enabled;
	}
	else {
    	new->enabled = add->enabled;
	}

    return new;
}
   
/* isAlphanum -- return true if the character is a letter, digit, underscore,
        dollar sign, or non-ASCII character.
*/

static int
isAlphanum(int c)
{
    return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
        (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
        c > 126);
//    return ( isalnum(c) || c == '_' || c == '$' || c== '\\' || c > 126);
}


/* get -- return the next character from stdin. Watch out for lookahead. If
        the character is a control character, translate it to a space or
        linefeed.
*/

static int
get(inbuffer *inbuf)
{
    int c = inbuf->lookahead;
    apr_status_t rc;
    inbuf->lookahead = EOF;
    if (c == EOF) {
        if ( inbuf->inbufpos >= inbuf->inlength ) {
//            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "INEOF %d",inbuf->inbufpos );
            return EOF;
        }
        else {
            c = inbuf->inbuf[inbuf->inbufpos++];
        }
//        rc = apr_file_getc(&c, inbuf->infile);
    } 
    if (c >= ' ' || c == '\n' || c == EOF ) {
        return c;
    }
    if (c == '\r') {
        return '\n';
    }
    if ( c<= 0) {
        return c;
    }
//    ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "IN -%d %d %c",inbuf->inbufpos, inbuf->inlength, c );
 
    return ' ';
}


/* peek -- get the next character without getting it.
*/

static int
peek(inbuffer *inbuf)
{
    inbuf->lookahead = get(inbuf);
    return inbuf->lookahead;
}


/* next -- get the next character, excluding comments. peek() is used to see
        if a '/' is followed by a '/' or '*'.
*/

static apr_status_t
next(inbuffer *inbuf,apr_bucket_brigade *bb)
{
    int c = get(inbuf);
    if  (c == '/') {
        switch (peek(inbuf)) {
        case '/':
            for (;;) {
                c = get(inbuf);
                if (c <= '\n') {
                    //*ret = c;
                    inbuf->B = c;
                    return APR_SUCCESS;
                 
                }
            }
        case '*':
            get(inbuf);
            for (;;) {
                switch (get(inbuf)) {
                case '*':
                    if (peek(inbuf) == '/') {
                        get(inbuf);
                        inbuf->B = ' ';
                        return APR_SUCCESS;
                    
                    }
                    break;
                case EOF:
                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                            "mod_jsmin:Unterminated comment");
                    return APR_EGENERAL;

                }
            }
        default:
            inbuf->B = c;
            return APR_SUCCESS;

        }
    }
    inbuf->B = c;
    return APR_SUCCESS;    
}


/* action -- do something! What you do is determined by the argument:
        1   Output A. Copy B to A. Get the next B.
        2   Copy B to A. Get the next B. (Delete A).
        3   Get the next B. (Delete B).
   action treats a string as a single character. Wow!
   action recognizes a regular expression if it is preceded by ( or , or =.
*/

static apr_status_t action_getnext( inbuffer *inbuf, apr_bucket_brigade *bb) {
    apr_status_t rc;
    rc = next( inbuf, bb );
    if (rc != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "mod_jsmin:action_getnext");
        return rc;
        
    }
        
    if (inbuf->B == '/' && ( inbuf->A == '(' || inbuf->A == ',' || inbuf->A == '=' ||
                         inbuf->A == ':' || inbuf->A == '[' || inbuf->A == '!' || 
                         inbuf->A == '&' || inbuf->A == '|' || inbuf->A == '?' || 
                         inbuf->A == '{' || inbuf->A == '}' || inbuf->A == ';' || 
                         inbuf->A == '\n')) {
        bb_rputc( inbuf, inbuf->A, bb);
        bb_rputc( inbuf, inbuf->B, bb);

        for (;;) {
            inbuf->A = get(inbuf);
            if (inbuf->A == '/') {
                break;
            } else if (inbuf->A =='\\') {
                bb_rputc(inbuf, inbuf->A, bb);
                inbuf->A = get(inbuf);
            } else if (inbuf->A <= '\n') {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                            "mod_jsmin:unterminated Regular Expression literal %c ", inbuf->A);                
                return APR_EGENERAL;

            }
            bb_rputc(inbuf, inbuf->A, bb);
        }
        rc = next(inbuf, bb);
        if (rc != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "mod_jsmin:action_getnext 2");
            return rc;
        }
    }    
    return APR_SUCCESS;
}
static apr_status_t action_copy( inbuffer *inbuf, apr_bucket_brigade *bb) {

    inbuf->A = inbuf->B;
    if (inbuf->A == '\'' || inbuf->A == '"') {
        for (;;) {
            bb_rputc(inbuf, inbuf->A, bb);
            inbuf->A = get( inbuf );
            if (inbuf->A == inbuf->B) {
                break;
            }
            if (inbuf->A <= '\n' && inbuf->A >= 0) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                            "mod_jsmin:unterminated string literal %c", inbuf->A);

                return APR_EGENERAL;
            }
            if (inbuf->A == '\\') {
                bb_rputc(inbuf, inbuf->A, bb);
                inbuf->A = get(inbuf);
                if (inbuf->A <= '\n'  && inbuf->A >= 0 ) {
                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                                "mod_jsmin:unterminated string literal %c", '\\');

                    return APR_EGENERAL;
                }
            }
        }
    }  
    return APR_SUCCESS;
}


/* jsmin -- Copy the input to the output, deleting the characters which are
        insignificant to JavaScript. Comments will be removed. Tabs will be
        replaced with spaces. Carriage returns will be replaced with linefeeds.
        Most spaces and linefeeds will be removed.
*/

static apr_status_t
jsmin_int( inbuffer *inbuf, apr_bucket_brigade *bb)
{
    apr_status_t rc;
    apr_bucket *b;

    rc = action_getnext( inbuf, bb);
    if (rc != APR_SUCCESS ) {
        return rc;
    }
    while (inbuf->A != EOF  ) {
        switch (inbuf->A) {
        case ' ':
            if (isAlphanum(inbuf->B)) {
                bb_rputc( inbuf, inbuf->A, bb);
                rc = action_copy(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
                rc = action_getnext(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
            } else {
                rc = action_copy(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
                rc = action_getnext(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
            }
            break;
        case '\n':
            switch (inbuf->B) {
            case '{':
            case '[':
            case '(':
            case '+':
            case '-':
               bb_rputc( inbuf, inbuf->A, bb);
               rc = action_copy(inbuf, bb);
               if (rc != APR_SUCCESS )
                   return rc;
               rc = action_getnext(inbuf, bb);
               if (rc != APR_SUCCESS )
                   return rc;
           break;
            case ' ':
                rc = action_getnext(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
                break;
            default:
                if (isAlphanum(inbuf->B)) {
                    bb_rputc( inbuf, inbuf->A, bb);
                    rc = action_copy(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                    rc = action_getnext(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                } else {
                    rc = action_copy(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                    rc = action_getnext(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                }
            }
            break;
        default:
            switch (inbuf->B) {
            case ' ':
                if (isAlphanum(inbuf->A)) {
                    bb_rputc( inbuf, inbuf->A, bb);
                    rc = action_copy(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                    rc = action_getnext(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                    break;
                }
                rc = action_getnext(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
                break;
            case '\n':
                switch (inbuf->A) {
                case '}':
                case ']':
                case ')':
                case '+':
                case '-':
                case '"':
                case '\'':
                    bb_rputc( inbuf, inbuf->A, bb);
                    rc = action_copy(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                    rc = action_getnext(inbuf, bb);
                    if (rc != APR_SUCCESS )
                        return rc;
                    break;
                default:
                    if (isAlphanum(inbuf->A)) {
                        bb_rputc( inbuf, inbuf->A, bb);
                        rc = action_copy(inbuf, bb);
                        if (rc != APR_SUCCESS )
                            return rc;
                        rc = action_getnext(inbuf, bb);
                        if (rc != APR_SUCCESS )
                            return rc;
                    } else {
                        rc = action_getnext(inbuf, bb);
                        if (rc != APR_SUCCESS )
                            return rc;
                    }
                }
                break;
            default:
                bb_rputc( inbuf, inbuf->A, bb);
                rc = action_copy(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
                rc = action_getnext(inbuf, bb);
                if (rc != APR_SUCCESS )
                    return rc;
                break;
            }
        }
    }
    if (inbuf->outbufpos >0)  {
        apr_bucket *b = apr_bucket_transient_create((char*)inbuf->outbuf, 
                inbuf->outbufpos*sizeof(UTF8), bb->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, b);    
    }

    b = apr_bucket_eos_create(bb->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);
    return APR_SUCCESS;
}

static int jsmin_handler(request_rec *r)
{
	jsmin_config_rec *conf;
    conn_rec *c = r->connection;	
	
    apr_file_t *f = NULL;
	apr_size_t length=0;
	apr_time_t mtime;

	char *file_string;
	char *token;
    char *strtokstate;
	apr_bucket_brigade *bb;
	apr_finfo_t finfo;
    inbuffer inbuf;

	apr_bucket *b;
    apr_status_t rv;
    if (strcmp(r->handler, "jsmin")) {
        return DECLINED;
    }
    
	conf = (jsmin_config_rec *) ap_get_module_config(r->per_dir_config, &jsmin_module);
	if (conf->enabled != 1)
        return DECLINED;

    /* we are now certain it is for us... */
    r->allowed |= (AP_METHOD_BIT << M_GET);
    if (r->method_number != M_GET) {
		return HTTP_METHOD_NOT_ALLOWED;
	}

    char *filename = r->filename;
	if ((rv = apr_file_open(&f, filename, APR_READ, APR_OS_DEFAULT,
			r->pool)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                    "mod_jsmin:file permissions deny server access: %s %s", filename,r->uri);
        return HTTP_FORBIDDEN;
	}
	if (( rv = apr_file_info_get( &finfo, APR_FINFO_MIN, f))!= APR_SUCCESS )  {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                    "mod_jsmin:file info failure: %s", filename);
        return HTTP_INTERNAL_SERVER_ERROR;
	}
	if (finfo.filetype != APR_REG) {
        return DECLINED;
	}
    if (finfo.size > JSMIN_MAX_BUFFER_SIZE ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                    "mod_jsmin:file too large: %s", filename);
        return DECLINED;
    }
	length = finfo.size;
	mtime = finfo.mtime;
//    inbuf.infile = f;
    inbuf.inlength = length;
    inbuf.outlength = length;
    inbuf.p = r->pool;
    inbuf.lookahead = EOF;
    inbuf.A = '\n';
    inbuf.B = 0;
    inbuf.inbufpos=0;
    inbuf.inbuf = apr_palloc( inbuf.p, length+1 );
    inbuf.inbuf[length]=0;
    apr_file_read( f, (void*)inbuf.inbuf, &length );
    if (finfo.size != length ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                    "mod_jsmin:couldn't read the file %s", filename);
        return DECLINED;
    }
    inbuf.outbufpos=0;
    inbuf.outbuf = apr_palloc( inbuf.p, JSMIN_BUFFER_SIZE );
    bb = apr_brigade_create(r->pool, c->bucket_alloc);
    if (!bb) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                "mod_jsmin:can't create my brigade: %s", r->filename);
        return HTTP_INTERNAL_SERVER_ERROR;        
    }
    rv = jsmin_int( &inbuf, bb);
    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                "mod_jsmin:file : %s %s", r->filename,r->uri);
    
    if ( rv != APR_SUCCESS ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                "mod_jsmin:file failure: %s", r->filename);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

	ap_set_content_length(r, inbuf.outlength);
//	apr_table_unset(r->headers_out, "ETag");
	r->mtime = mtime;
	ap_set_last_modified(r);
    return ap_pass_brigade(r->output_filters, bb);
    if (rv != APR_SUCCESS) {
         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                       "mod_jsmin: ap_pass_brigade failed for uri %s", r->uri);
         return HTTP_INTERNAL_SERVER_ERROR;
    }
    return OK;

}

static int jsmin_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    apr_bucket *e;
    apr_size_t maxlen = 0; /* size in bytes of the complete data */
    request_rec *r;
    int i=0;
    r = f->r;
    apr_status_t rv;
    

    jsmin_ctx *ctx = f->ctx;
    // omniture_config *c; 
    if (!ctx) {
        /* We're cool with filtering this. */
        ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
        ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
        ctx->p = f->r->pool;
        apr_table_unset(f->r->headers_out, "Content-Length");
    } 
    /* 
    else {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "2nd time in");
    } 
    */
    for (e = APR_BRIGADE_FIRST(bb);
         e != APR_BRIGADE_SENTINEL(bb);
         e = APR_BUCKET_NEXT(e))
    {
        const char *data;
        apr_bucket *cpy;
        
        apr_size_t len;
        /*
        i++;
        if (APR_BUCKET_IS_METADATA(e)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR,0,r, "bucket %d - %s METADATA",i, e->type->name);            
        }
        else {
            ap_log_rerror(APLOG_MARK, APLOG_ERR,0,r, "bucket %d - %s len %d", i, e->type->name, e->length);
        }
        */
        
        if (APR_BUCKET_IS_EOS(e)) {
            char *buffer;
            apr_bucket_brigade *new_bb;
            apr_bucket_copy(e, &cpy);
            APR_BRIGADE_INSERT_TAIL(ctx->bb, cpy);
            
            APR_BUCKET_REMOVE(e);

            /* Okay, we've seen the EOS.
             * Time to pass it along down the chain.
             * if something fails from here, just serve something if we can.
             */

             if (maxlen > JSMIN_MAX_BUFFER_SIZE) {
                  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_jsmin:"
                                 "The size of the data is bigger to handle"
                                 ", so no attempt to insert tags");
                  return ap_pass_brigade(f->next, ctx->bb);
              }
              rv = apr_brigade_pflatten(ctx->bb, (char**)&buffer, &len,f->r->pool);
              
              if (rv != APR_SUCCESS) {
                  ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_jsmin:"
                                                     "apr_brigade_pflatten");
                  return ap_pass_brigade(f->next, ctx->bb);
              }
              else {
                  new_bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
                  // do stuff here
                  inbuffer inbuf;
                  inbuf.inlength = len;
                  inbuf.outlength = 0;
                  inbuf.p = f->r->pool;
                  inbuf.lookahead = EOF;
                  inbuf.A = '\n';
                  inbuf.B = 0;
                  inbuf.inbufpos=0;
                  inbuf.inbuf =(signed char*) buffer; //apr_palloc( inbuf.p, len+1 );
                  inbuf.inbuf[len]=0;
                  inbuf.outbufpos=0;
                  inbuf.outbuf = apr_palloc( inbuf.p, JSMIN_BUFFER_SIZE );

                  rv = jsmin_int( &inbuf, new_bb);
                  ap_set_content_length(f->r, inbuf.outlength);
                  if ( rv != APR_SUCCESS ) {
                      ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                                "mod_jsmin:filter: %s %s", r->filename,r->uri);
                      return ap_pass_brigade(f->next, ctx->bb);
                  }
                  apr_brigade_destroy(ctx->bb);
                  return ap_pass_brigade(f->next, new_bb);
              }
        }
        if (!APR_BUCKET_IS_METADATA(e)) {
            apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
            maxlen += len;
        }
        if ( APR_BUCKET_IS_TRANSIENT(e)) {
            //apr_bucket *pool_version;
            char *d = apr_pmemdup(r->pool, data,len);
            cpy = apr_bucket_pool_create( d, len, r->pool, f->c->bucket_alloc);
            //ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, data);            
        }
        else {
            apr_bucket_copy(e, &cpy);
        }
        APR_BRIGADE_INSERT_TAIL(ctx->bb, cpy);
        APR_BUCKET_REMOVE(e);
    }

    return APR_SUCCESS;

}
static void register_hooks(apr_pool_t *p)
{
  ap_hook_handler(jsmin_handler,NULL,NULL,APR_HOOK_MIDDLE);

  ap_register_output_filter("JSMIN", jsmin_output_filter, NULL, AP_FTYPE_CONTENT_SET);

}

module AP_MODULE_DECLARE_DATA jsmin_module =
{
    STANDARD20_MODULE_STUFF,
    create_jsmin_config,    /* dir config creater */
    merge_jsmin_configs,    /* dir merger --- default is to override */
    NULL,                       /* server config */
    NULL,                       /* merge server config */
    jsmin_cmds,             /* command apr_table_t */
    register_hooks              /* register hooks */
};
